﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

// Kerem KAT
// Coordinate Transformation Library.
namespace KorTransLib
{
    public abstract class Coordinate
    {
        #region Binary Operators
        public static Coordinate operator +(Coordinate c1, Coordinate c2)
        {
            DecimalDegreeCoordinate dd1 = (DecimalDegreeCoordinate)TypeDescriptor.GetConverter(c1).ConvertTo(c1, typeof(DecimalDegreeCoordinate));
            DecimalDegreeCoordinate dd2 = (DecimalDegreeCoordinate)TypeDescriptor.GetConverter(c2).ConvertTo(c2, typeof(DecimalDegreeCoordinate));

            return new DecimalDegreeCoordinate(dd1.lat + dd2.lat, dd1.lon + dd2.lon);
        }

        public static Coordinate operator -(Coordinate c1, Coordinate c2)
        {
            return c1 + -c2;
        }
        #endregion

        #region Scalar Binary Operators
        public static Coordinate operator *(Coordinate c1, double d)
        {
            DecimalDegreeCoordinate dd1 = (DecimalDegreeCoordinate)TypeDescriptor.GetConverter(c1).ConvertTo(c1, typeof(DecimalDegreeCoordinate));

            return new DecimalDegreeCoordinate(d * dd1.lat, d * dd1.lon);
        }

        public static Coordinate operator /(Coordinate c1, double d)
        {
            DecimalDegreeCoordinate dd1 = (DecimalDegreeCoordinate)TypeDescriptor.GetConverter(c1).ConvertTo(c1, typeof(DecimalDegreeCoordinate));

            return new DecimalDegreeCoordinate(dd1.lat / d, dd1.lon / d);
        }
        #endregion

        #region Unary Operators
        public static Coordinate operator -(Coordinate c1)
        {
            DecimalDegreeCoordinate dd1 = (DecimalDegreeCoordinate)TypeDescriptor.GetConverter(c1).ConvertTo(c1, typeof(DecimalDegreeCoordinate));
            return new DecimalDegreeCoordinate(-dd1.lat, -dd1.lon);
        }

        public static Coordinate operator +(Coordinate c1)
        {
            DecimalDegreeCoordinate dd1 = (DecimalDegreeCoordinate)TypeDescriptor.GetConverter(c1).ConvertTo(c1, typeof(DecimalDegreeCoordinate));
            return dd1;
        }
        #endregion
    }

    #region Coordinate Types
    [TypeConverter(typeof(DecimalDegreeCoordinateConverter))]
    public class DecimalDegreeCoordinate : Coordinate
    {
        /// <summary>
        /// Geodetic latitude.
        /// </summary>
        public double lat { get; set; }

        /// <summary>
        /// Geodetic longtitude.
        /// </summary>
        public double lon { get; set; }

        public DecimalDegreeCoordinate() { }
        public DecimalDegreeCoordinate(double lat, double lon)
        {
            this.lat = lat;
            this.lon = lon;
        }

        public override string ToString()
        {
            return string.Format("{0:F6} {1} {2:F6} {3}", Math.Abs(lat), lat > 0 ? "K" : "G", Math.Abs(lon), lon > 0 ? "D" : "B");
        }
    }

    [TypeConverter(typeof(DegreeMinuteSecondCoordinateConverter))]
    public class DegreeMinuteSecondCoordinate : Coordinate
    {
        public int latDegree { get; set; }
        public int latMinute { get; set; }
        public double latSecond { get; set; }
        public int lonDegree { get; set; }
        public int lonMinute { get; set; }
        public double lonSecond { get; set; }

        public DegreeMinuteSecondCoordinate() { }
        public DegreeMinuteSecondCoordinate(int latDegree, int latMinute, double latSecond, int lonDegree, int lonMinute, double lonSecond)
        {
            this.latDegree = latDegree;
            this.latMinute = latMinute;
            this.latSecond = latSecond;
            this.lonDegree = lonDegree;
            this.lonMinute = lonMinute;
            this.lonSecond = lonSecond;
        }

        public override string ToString()
        {
            return string.Format("{0}° {1}' {2:F2}\" {6} {3}° {4}' {5:F2}\" {7}", latDegree, latMinute, latSecond, lonDegree, lonMinute, lonSecond, (latDegree * latMinute * latSecond > 0) ? "K" : "G", (lonDegree * lonMinute * lonSecond > 0) ? "D" : "B");
        }
    }

    [TypeConverter(typeof(UTMCoordinateConverter))]
    public class UTMCoordinate : Coordinate
    {
        private string zone;
        public string Zone { 
            get { return zone; }
            set
            {
                if (!checkZone(value))
                    throw new ArgumentException("UTM Zone değeri geçersiz.", "Zone");
                zone = value;
            } 
        }
        public double Sağa { get; set; }
        public double Yukarı { get; set; }

        public enum Doğruluk
        {
            m1000, m100, m10, m1
        }

        public UTMCoordinate() { }
        public UTMCoordinate(string Zone, double Sağa, double Yukarı)
        {
            if (!checkZone(Zone))
                throw new ArgumentException("UTM Zone değeri geçersiz.", "Zone");

            this.Zone = Zone;
            this.Sağa = Sağa;
            this.Yukarı = Yukarı;
        }

        private bool checkZone(string Zone)
        {
            if (Zone.Length >= 2 && Zone.Length <= 3)
            {
                if (Zone[Zone.Length - 1] >= 'C' && Zone[Zone.Length - 1] <= 'X')
                {
                    int res;
                    if (int.TryParse(Zone.Substring(0, 2), out res))
                    {
                        if (res >= 1 && res <= 60)
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        public override string ToString()
        {
            return string.Format("{0} {1:F0} {2:F0}", Zone, Sağa, Yukarı);
        }

        public string ToString(Doğruluk doğruluk)
        {
            string tKısaSağa = Sağa.ToString("F0");
            int kısaSağa = int.Parse(tKısaSağa.Substring(1));

            string tKısaYukarı = Yukarı.ToString("F0");
            int kısaYukarı = int.Parse(tKısaYukarı.Substring(2));

            string ret = "";
            switch (doğruluk)
            {
                case Doğruluk.m1:
                    ret = string.Format("{0} {1:F0} {2:F0}", Zone, kısaSağa, kısaYukarı); break;
                case Doğruluk.m10:
                    ret = string.Format("{0} {1:F0} {2:F0}", Zone, kısaSağa / 10, kısaYukarı / 10); break;
                case Doğruluk.m100:
                    ret = string.Format("{0} {1:F0} {2:F0}", Zone, kısaSağa / 100, kısaYukarı / 100); break;
                case Doğruluk.m1000:
                    ret = string.Format("{0} {1:F0} {2:F0}", Zone, kısaSağa / 1000, kısaYukarı / 1000); break;
            }
            return ret;
        }

    }
    #endregion

    #region Coordinate Type Converters
    public class DecimalDegreeCoordinateConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context,
           Type sourceType)
        {
            if (sourceType == typeof(DegreeMinuteSecondCoordinate)
                || sourceType == typeof(UTMCoordinate)
                || sourceType == typeof(DecimalDegreeCoordinate))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is DegreeMinuteSecondCoordinate)
            {
                return CoordinateConverterMath.DMStoDD((DegreeMinuteSecondCoordinate)value);
            }
            else if (value is UTMCoordinate)
            {
                return CoordinateConverterMath.UTMtoDD((UTMCoordinate)value);
            }
            else if (value is DecimalDegreeCoordinate)
            {
                return value;
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(DegreeMinuteSecondCoordinate)
                || destinationType == typeof(UTMCoordinate)
                || destinationType == typeof(DecimalDegreeCoordinate))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(DegreeMinuteSecondCoordinate))
            {
                return CoordinateConverterMath.DDtoDMS((DecimalDegreeCoordinate)value);
            }
            else if (destinationType == typeof(UTMCoordinate))
            {
                return CoordinateConverterMath.DDtoUTM((DecimalDegreeCoordinate)value);
            }
            else if (destinationType == typeof(DecimalDegreeCoordinate))
            {
                return value;
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    public class DegreeMinuteSecondCoordinateConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context,
           Type sourceType)
        {
            if (sourceType == typeof(DecimalDegreeCoordinate)
                || sourceType == typeof(UTMCoordinate)
                || sourceType == typeof(DegreeMinuteSecondCoordinate))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is DecimalDegreeCoordinate)
            {
                return CoordinateConverterMath.DDtoDMS((DecimalDegreeCoordinate)value);
            }
            else if (value is UTMCoordinate)
            {
                return CoordinateConverterMath.UTMtoDMS((UTMCoordinate)value);
            }
            else if (value is DegreeMinuteSecondCoordinate)
            {
                return value;
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(DecimalDegreeCoordinate)
                || destinationType == typeof(UTMCoordinate)
                || destinationType == typeof(DegreeMinuteSecondCoordinate))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(DecimalDegreeCoordinate))
            {
                return CoordinateConverterMath.DMStoDD((DegreeMinuteSecondCoordinate)value);
            }
            else if (destinationType == typeof(UTMCoordinate))
            {
                return CoordinateConverterMath.DMStoUTM((DegreeMinuteSecondCoordinate)value);
            }
            else if (destinationType == typeof(DegreeMinuteSecondCoordinate))
            {
                return value;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    public class UTMCoordinateConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context,
           Type sourceType)
        {
            if (sourceType == typeof(DecimalDegreeCoordinate)
                || sourceType == typeof(DegreeMinuteSecondCoordinate)
                || sourceType == typeof(UTMCoordinate))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is DecimalDegreeCoordinate)
            {
                return CoordinateConverterMath.DDtoUTM((DecimalDegreeCoordinate)value);
            }
            else if (value is DegreeMinuteSecondCoordinate)
            {
                return CoordinateConverterMath.DMStoUTM((DegreeMinuteSecondCoordinate)value);
            }
            else if (value is UTMCoordinate)
            {
                return value;
            }
            return base.ConvertFrom(context, culture, value);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            if (destinationType == typeof(DecimalDegreeCoordinate)
                || destinationType == typeof(DegreeMinuteSecondCoordinate)
                || destinationType == typeof(UTMCoordinate))
            {
                return true;
            }
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(DecimalDegreeCoordinate))
            {
                return CoordinateConverterMath.UTMtoDD((UTMCoordinate)value);
            }
            else if (destinationType == typeof(DegreeMinuteSecondCoordinate))
            {
                return CoordinateConverterMath.UTMtoDMS((UTMCoordinate)value);
            }
            else if (destinationType == typeof(UTMCoordinate))
            {
                return value;
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    #endregion

    public class CoordinateConverterMath
    {
        class Ellipsoid
        {
            public Ellipsoid() { }
            public Ellipsoid(int Id, string name, double radius, double ecc)
            {
                id = Id; ellipsoidName = name;
                EquatorialRadius = radius; eccentricitySquared = ecc;
            }

            int id;
            string ellipsoidName;
            public double EquatorialRadius { get; set; }
            public double eccentricitySquared { get; set; }

        }

        const double PI = Math.PI;
        const double FOURTHPI = PI / 4;
        const double deg2rad = PI / 180;
        const double rad2deg = 180.0 / PI;

        static Ellipsoid[] ellipsoid = new Ellipsoid[]
        {//  id, Ellipsoid name, Equatorial Radius, square of eccentricity	
	    new Ellipsoid( -1, "Placeholder", 0, 0),//placeholder only, To allow array indices to match id numbers
	    new Ellipsoid( 1, "Airy", 6377563, 0.00667054),
	    new Ellipsoid( 2, "Australian National", 6378160, 0.006694542),
	    new Ellipsoid( 3, "Bessel 1841", 6377397, 0.006674372),
	    new Ellipsoid( 4, "Bessel 1841 (Nambia) ", 6377484, 0.006674372),
	    new Ellipsoid( 5, "Clarke 1866", 6378206, 0.006768658),
	    new Ellipsoid( 6, "Clarke 1880", 6378249, 0.006803511),
	    new Ellipsoid( 7, "Everest", 6377276, 0.006637847),
	    new Ellipsoid( 8, "Fischer 1960 (Mercury) ", 6378166, 0.006693422),
	    new Ellipsoid( 9, "Fischer 1968", 6378150, 0.006693422),
	    new Ellipsoid( 10, "GRS 1967", 6378160, 0.006694605),
	    new Ellipsoid( 11, "GRS 1980", 6378137, 0.00669438),
	    new Ellipsoid( 12, "Helmert 1906", 6378200, 0.006693422),
	    new Ellipsoid( 13, "Hough", 6378270, 0.00672267),
	    new Ellipsoid( 14, "International", 6378388, 0.00672267),
	    new Ellipsoid( 15, "Krassovsky", 6378245, 0.006693422),
	    new Ellipsoid( 16, "Modified Airy", 6377340, 0.00667054),
	    new Ellipsoid( 17, "Modified Everest", 6377304, 0.006637847),
	    new Ellipsoid( 18, "Modified Fischer 1960", 6378155, 0.006693422),
	    new Ellipsoid( 19, "South American 1969", 6378160, 0.006694542),
	    new Ellipsoid( 20, "WGS 60", 6378165, 0.006693422),
	    new Ellipsoid( 21, "WGS 66", 6378145, 0.006694542),
	    new Ellipsoid( 22, "WGS-72", 6378135, 0.006694318),
	    new Ellipsoid( 23, "WGS-84", 6378137, 0.0066943800042608065155834872766664)                                       };

        public static DecimalDegreeCoordinate DMStoDD(DegreeMinuteSecondCoordinate dmsCoord)
        {
            DecimalDegreeCoordinate ret = new DecimalDegreeCoordinate();
            ret.lat = dmsCoord.latDegree + dmsCoord.latMinute / 60.0 + dmsCoord.latSecond / 60.0 / 60.0;
            ret.lon = dmsCoord.lonDegree + dmsCoord.lonMinute / 60.0 + dmsCoord.lonSecond / 60.0 / 60.0;
            return ret;
        }

        public static DegreeMinuteSecondCoordinate DDtoDMS(DecimalDegreeCoordinate ddCoord)
        {
            DegreeMinuteSecondCoordinate ret = new DegreeMinuteSecondCoordinate();
            ret.latDegree = (int)ddCoord.lat;
            ret.latMinute = (int)((ddCoord.lat - ret.latDegree) * 60);
            ret.latSecond = (((ddCoord.lat - ret.latDegree) * 60) - ret.latMinute) * 60;

            ret.lonDegree = (int)ddCoord.lon;
            ret.lonMinute = (int)((ddCoord.lon - ret.lonDegree) * 60);
            ret.lonSecond = (((ddCoord.lon - ret.lonDegree) * 60) - ret.lonMinute) * 60;

            return ret;
        }

        //spheroid etc.
        public static UTMCoordinate DDtoUTM(DecimalDegreeCoordinate ddCoord)
        {
            return DDtoUTM(23, ddCoord);
        }

        public static UTMCoordinate DDtoUTM(int ReferenceEllipsoid, DecimalDegreeCoordinate ddCoord)
        {
            //converts lat/long to UTM coords.  Equations from USGS Bulletin 1532 
            //East Longitudes are positive, West longitudes are negative. 
            //North latitudes are positive, South latitudes are negative
            //Lat and Long are in decimal degrees
            //Written by Chuck Gantz- chuck.gantz@globalstar.com
            UTMCoordinate ret = new UTMCoordinate();

            double Long = ddCoord.lon;
            double Lat = ddCoord.lat;

            double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius;
            double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared;
            double k0 = 0.9996;

            double LongOrigin;
            double eccPrimeSquared;
            double N, T, C, A, M;

            //Make sure the longitude is between -180.00 .. 179.9
            double LongTemp = (Long + 180) - (int)((Long + 180) / 360) * 360 - 180; // -180.00 .. 179.9;

            double LatRad = Lat * deg2rad;
            double LongRad = LongTemp * deg2rad;
            double LongOriginRad;
            int ZoneNumber;

            ZoneNumber = (int)((LongTemp + 180) / 6) + 1;

            if (Lat >= 56.0 && Lat < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0)
                ZoneNumber = 32;

            // Special zones for Svalbard
            if (Lat >= 72.0 && Lat < 84.0)
            {
                if (LongTemp >= 0.0 && LongTemp < 9.0) ZoneNumber = 31;
                else if (LongTemp >= 9.0 && LongTemp < 21.0) ZoneNumber = 33;
                else if (LongTemp >= 21.0 && LongTemp < 33.0) ZoneNumber = 35;
                else if (LongTemp >= 33.0 && LongTemp < 42.0) ZoneNumber = 37;
            }
            LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3;  //+3 puts origin in middle of zone
            LongOriginRad = LongOrigin * deg2rad;

            //compute the UTM Zone from the latitude and longitude
            ret.Zone = string.Format("{0}{1}", ZoneNumber, UTMLetterDesignator(Lat));


            eccPrimeSquared = (eccSquared) / (1 - eccSquared);

            N = a / Math.Sqrt(1 - eccSquared * Math.Sin(LatRad) * Math.Sin(LatRad));
            T = Math.Tan(LatRad) * Math.Tan(LatRad);
            C = eccPrimeSquared * Math.Cos(LatRad) * Math.Cos(LatRad);
            A = Math.Cos(LatRad) * (LongRad - LongOriginRad);

            M = a * ((1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad
                        - (3 * eccSquared / 8 + 3 * eccSquared * eccSquared / 32 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.Sin(2 * LatRad)
                                            + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.Sin(4 * LatRad)
                                            - (35 * eccSquared * eccSquared * eccSquared / 3072) * Math.Sin(6 * LatRad));

            ret.Sağa = (double)(k0 * N * (A + (1 - T + C) * A * A * A / 6
                            + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120)
                            + 500000.0);

            ret.Yukarı = (double)(k0 * (M + N * Math.Tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24
                         + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720)));
            if (Lat < 0)
                ret.Yukarı += 10000000.0; //10000000 meter offset for southern hemisphere

            return ret;
        }

        static char UTMLetterDesignator(double Lat)
        {
            //This routine determines the correct UTM letter designator for the given latitude
            //returns 'Z' if latitude is outside the UTM limits of 84N to 80S
            //Written by Chuck Gantz- chuck.gantz@globalstar.com
            char LetterDesignator;

            if ((84 >= Lat) && (Lat >= 72)) LetterDesignator = 'X';
            else if ((72 > Lat) && (Lat >= 64)) LetterDesignator = 'W';
            else if ((64 > Lat) && (Lat >= 56)) LetterDesignator = 'V';
            else if ((56 > Lat) && (Lat >= 48)) LetterDesignator = 'U';
            else if ((48 > Lat) && (Lat >= 40)) LetterDesignator = 'T';
            else if ((40 > Lat) && (Lat >= 32)) LetterDesignator = 'S';
            else if ((32 > Lat) && (Lat >= 24)) LetterDesignator = 'R';
            else if ((24 > Lat) && (Lat >= 16)) LetterDesignator = 'Q';
            else if ((16 > Lat) && (Lat >= 8)) LetterDesignator = 'P';
            else if ((8 > Lat) && (Lat >= 0)) LetterDesignator = 'N';
            else if ((0 > Lat) && (Lat >= -8)) LetterDesignator = 'M';
            else if ((-8 > Lat) && (Lat >= -16)) LetterDesignator = 'L';
            else if ((-16 > Lat) && (Lat >= -24)) LetterDesignator = 'K';
            else if ((-24 > Lat) && (Lat >= -32)) LetterDesignator = 'J';
            else if ((-32 > Lat) && (Lat >= -40)) LetterDesignator = 'H';
            else if ((-40 > Lat) && (Lat >= -48)) LetterDesignator = 'G';
            else if ((-48 > Lat) && (Lat >= -56)) LetterDesignator = 'F';
            else if ((-56 > Lat) && (Lat >= -64)) LetterDesignator = 'E';
            else if ((-64 > Lat) && (Lat >= -72)) LetterDesignator = 'D';
            else if ((-72 > Lat) && (Lat >= -80)) LetterDesignator = 'C';
            else LetterDesignator = 'Z'; //This is here as an error flag to show that the Latitude is outside the UTM limits

            return LetterDesignator;
        }

        public static DecimalDegreeCoordinate UTMtoDD(UTMCoordinate utmCoord)
        {
            return UTMtoDD(23, utmCoord);
        }

        public static DecimalDegreeCoordinate UTMtoDD(int ReferenceEllipsoid, UTMCoordinate utmCoord)
        {
            //converts UTM coords to lat/long.  Equations from USGS Bulletin 1532 
            //East Longitudes are positive, West longitudes are negative. 
            //North latitudes are positive, South latitudes are negative
            //Lat and Long are in decimal degrees. 
            //Written by Chuck Gantz- chuck.gantz@globalstar.com
            DecimalDegreeCoordinate ret = new DecimalDegreeCoordinate();
            double k0 = 0.9996;
            double a = ellipsoid[ReferenceEllipsoid].EquatorialRadius;
            double eccSquared = ellipsoid[ReferenceEllipsoid].eccentricitySquared;
            double eccPrimeSquared;
            double e1 = (1 - Math.Sqrt(1 - eccSquared)) / (1 + Math.Sqrt(1 - eccSquared));
            double N1, T1, C1, R1, D, M;
            double LongOrigin;
            double mu, phi1, phi1Rad;
            double x, y;
            int ZoneNumber;
            char ZoneLetter;

            bool NorthernHemisphere = false; //1 for northern hemispher, 0 for southern

            x = utmCoord.Sağa - 500000.0; //remove 500,000 meter offset for longitude
            y = utmCoord.Yukarı;

            ZoneNumber = int.Parse(utmCoord.Zone.Substring(0, utmCoord.Zone.Length - 1));
            ZoneLetter = utmCoord.Zone.Substring(utmCoord.Zone.Length - 1)[0];
            if ((ZoneLetter - 'N') >= 0)
                NorthernHemisphere = true;//point is in northern hemisphere

            if (!NorthernHemisphere)
            {
                y -= 10000000.0;//remove 10,000,000 meter offset used for southern hemisphere
            }

            LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3;  //+3 puts origin in middle of zone

            eccPrimeSquared = (eccSquared) / (1 - eccSquared);

            M = y / k0;
            mu = M / (a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256));

            phi1Rad = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.Sin(2 * mu)
                        + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.Sin(4 * mu)
                        + (151 * e1 * e1 * e1 / 96) * Math.Sin(6 * mu);
            phi1 = phi1Rad * rad2deg;

            N1 = a / Math.Sqrt(1 - eccSquared * Math.Sin(phi1Rad) * Math.Sin(phi1Rad));
            T1 = Math.Tan(phi1Rad) * Math.Tan(phi1Rad);
            C1 = eccPrimeSquared * Math.Cos(phi1Rad) * Math.Cos(phi1Rad);
            R1 = a * (1 - eccSquared) / Math.Pow(1 - eccSquared * Math.Sin(phi1Rad) * Math.Sin(phi1Rad), 1.5);
            D = x / (N1 * k0);

            ret.lat = phi1Rad - (N1 * Math.Tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24
                            + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720);
            ret.lat *= rad2deg;

            ret.lon = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1)
                            * D * D * D * D * D / 120) / Math.Cos(phi1Rad);
            ret.lon = LongOrigin + ret.lon * rad2deg;
            return ret;
        }

        public static DegreeMinuteSecondCoordinate UTMtoDMS(UTMCoordinate utm)
        {
            return DDtoDMS(UTMtoDD(utm));
        }

        public static UTMCoordinate DMStoUTM(DegreeMinuteSecondCoordinate dms)
        {
            return DDtoUTM(DMStoDD(dms));
        }
    }
}
